#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <thread>
#include <iostream>
#include <map>

#include <glog/logging.h>
#include <glog/log_severity.h>

#include "kcp_agent.h"
#include "incoming_handle.h"
#include "utils.h"

#define GLOG_DIR "./log/"
#define GLOG_WRITE_FILE_DELAY  (30)  //seconds

static int g_sockfd = -1;
static struct sockaddr_in g_server_addr;
static short g_server_port = 7799;

static int glog_level = google::GLOG_INFO;//google::GLOG_WARNING;

typedef struct _KCPKEY
{
	unsigned int ip_;
	unsigned short port_;
	unsigned int conv_;
	
	_KCPKEY(unsigned int ip, unsigned short port, unsigned int conv):ip_(ip), port_(port), conv_(conv) {}
	
	bool operator< (const _KCPKEY& key) const
	{
		return (ip_ + port_ + conv_) < (key.ip_ + key.port_ + key.conv_);
	}
	
}KCPKEY;

static std::map<KCPKEY, KcpAgent *> KCPMAP;

int Init()
{	
	g_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	
	if(g_sockfd < 0)
	{
		perror("socket error！");
		exit(1);
	}

	int flags = fcntl(g_sockfd, F_GETFL, 0);
	flags |= O_NONBLOCK;

	if(fcntl(g_sockfd, F_SETFL, flags) == -1)
	{
		LOG(WARNING)<<"Can not set ASY module";
	}
	
	bzero(&g_server_addr, sizeof(g_server_addr));
	
	g_server_addr.sin_family = AF_INET;
	g_server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	g_server_addr.sin_port = htons(g_server_port);
		
	LOG(INFO)<<"server socket: "<<g_sockfd<<" port: "<<g_server_port;
	
	if(bind(g_sockfd,(struct sockaddr *)&(g_server_addr), sizeof(struct sockaddr_in)) < 0)
	{
		perror("bind");
		exit(1);
	}
}

void MainLoop()
{
	unsigned int len = sizeof(struct sockaddr_in);
	int n,ret;	
	
	struct sockaddr_in from;

	int maxfdp, nready;
	fd_set rset;
	struct timeval tv;
	
	maxfdp = g_sockfd + 1;
	
	FD_ZERO(&rset);
	
	bool out = false;
	
	while(1)
	{
		char buf[1024]={0};

		FD_ZERO(&rset);
		FD_SET(g_sockfd, &rset);

		tv.tv_sec = 0;
		tv.tv_usec = 10*1000;

		nready = select(maxfdp, &rset, NULL, NULL, &tv);
		
		if (nready < 0)
		{
			if (errno == EINTR)
			{
				continue;
			}
			else
			{
				perror("select error");
				exit(-1);
			}
		}
		else if (0 == nready)
		{
			{
				//std::unique_lock<std::mutex> lk(g_update_mtx);
				//g_ready = true;
			}
			
			//g_update_cv.notify_one();

			continue;
		}

		if (FD_ISSET(g_sockfd, &rset))
		{
			while(1)
			{
				n = recvfrom(g_sockfd, buf, 1024, 0, (struct sockaddr *)&from, &len); 	
				if (n <= 0)
				{
					break;
				}

				IUINT32 conv = (IUINT32)buf[0]&0xff | (IUINT32)((buf[1]&0xff)<<8) | (IUINT32)((buf[2]&0xff)<<16) | (IUINT32)((buf[3]&0xff)<<24);

				//printf("------- 0x%x 0x%x 0x%x 0x%x, 0x%x\n", (IUINT32)buf[0]&0xff, (IUINT32)(buf[1]&0xff), (IUINT32)(buf[2]&0xff), (IUINT32)(buf[3]&0xff), conv);

				KcpAgent *kcp_agent = NULL;

				KCPKEY kcp_key(from.sin_addr.s_addr, from.sin_port, conv);
				
				std::map<KCPKEY, KcpAgent *>::iterator it;
				
				it = KCPMAP.find(kcp_key);
				if (it != KCPMAP.end())
				{
					kcp_agent = it->second;
				} 
				else
				{
					kcp_agent = new KcpAgent;
					kcp_agent->SetClientAddr(&from);
					kcp_agent->SetFd(g_sockfd);
					kcp_agent->AllocKcp(conv);
					kcp_agent->Start();
					
					//KCPMAP.insert(std::make_pair(kcp_key, agent));
					KCPMAP[kcp_key] = kcp_agent;
					
					LOG(INFO)<<"insert kcp map(key: "<<kcp_key.ip_<<"-"<<kcp_key.port_<<"-"<<kcp_key.conv_<<", agent: "<<kcp_agent;
				}

				PktData data;
				data.size_ = n;
				memcpy(data.data_, buf, n);

				kcp_agent->Feed(data);
			}
		}
	}	
}

static void GlogSignalHandle(const char* data, int size)
{
	static int cnt = 0;
	
	if (0 == cnt)
		LOG(ERROR) << "segment falut info:\n";
	
	std::string str = data;
	LOG(ERROR) << str << std::endl;

	cnt++;
}

static void InitLogSetting(char *flag)
{
	google::InitGoogleLogging(flag);

	google::InstallFailureSignalHandler();
	google::InstallFailureWriter(&GlogSignalHandle); 

	FLAGS_servity_single_log = true;// 用来按照等级区分log文件

	FLAGS_logbufsecs = GLOG_WRITE_FILE_DELAY;//缓冲日志输出，默认为30秒，0为立即输出

	FLAGS_max_log_size = 10; //日志大小, 单位:MB

	FLAGS_stop_logging_if_full_disk = true;//当磁盘被写满时，停止日志输出

	FLAGS_minloglevel = glog_level; //低于此级别的日志不会被记录到文件或输出到控制台

	google::SetStderrLogging(glog_level); //高于google::GLOG_WARNING级别的日志同时输出到屏幕

	//日志名称和输出地址
	char directory[256] = {0};

	strcpy(directory, GLOG_DIR);

	strcat(directory, "log_");

	google::SetLogDestination(google::GLOG_INFO, directory);
	google::SetLogDestination(google::GLOG_WARNING, directory);
	google::SetLogDestination(google::GLOG_ERROR, directory);
	google::SetLogDestination(google::GLOG_FATAL, directory);
}

int main(int argc,char *argv[])
{
	printf("This is Signal-Server based on kcp\n");
	
	InitLogSetting(argv[0]);

	if(argc >= 2 )
	{
		g_server_port = atoi(argv[1]);
	}
	
	IncomingHandler *incomng_handler = new IncomingHandler(MessageDeliver::MSG_TYPE_SIGNAL);
	MessageDispatcher::Instance()->RegisterCommDeliver(incomng_handler);

	Init();
	
	MainLoop();
	
	return 0;	
}

